// Simple command-line kernel monitor useful for
// controlling the kernel and exploring the system interactively.

#include <inc/stdio.h>
#include <inc/string.h>
#include <inc/memlayout.h>
#include <inc/assert.h>
#include <inc/x86.h>

#include <kern/console.h>
#include <kern/monitor.h>
#include <kern/trap.h>
#include <kern/kdebug.h>
#include <kern/pmap.h>

#define CMDBUF_SIZE	80	// enough for one VGA text line


struct Command {
	const char *name;
	const char *desc;
	// return -1 to force monitor to exit
	int (*func)(int argc, char** argv, struct Trapframe* tf);
};

static struct Command commands[] = {
	{ "help", "Display this list of commands", mon_help },
	{ "kerninfo", "Display information about the kernel", mon_kerninfo },
	{ "color", "Test color console, will print the arguments in color", mon_color },
	{ "backtrace", "Display backtrace information", mon_backtrace },
	{ "showmappings", "Show physical page mappings", mon_showmappings },
	{ "setsu", "Set S/U bit for some linear address' corresponding page table entry", mon_setsu },
	{ "clrsu", "Clear S/U bit for some linear address' corresponding page table entry", mon_clrsu },
	{ "setrw", "Set R/W bit for some linear address' corresponding page table entry", mon_setrw },
	{ "clrrw", "Clear S/U bit for some linear address' corresponding page table entry", mon_clrrw },
	{ "dumpla", "Dump the contents in linear address range", mon_dumpla },
	{ "dumppa", "Dump the contents in physical address range", mon_dumppa },
	{ "pgstat", "Physical page status", mon_pgstat },
	{ "alloc_page", "Allocate a page", mon_alloc_page },
	{ "free_page", "Free a page", mon_free_page },
	{ "page_status", "Page allocation status", mon_page_status },
//	{ "c", "Continue execution", mon_continue },
//	{ "s", "Single-step execution", mon_step },
//	{ "u", "Disassemble next instruction", mon_disasm },
};
#define NCOMMANDS (sizeof(commands)/sizeof(commands[0]))

unsigned read_eip();

/***** Implementations of basic kernel monitor commands *****/

int
mon_help(int argc, char **argv, struct Trapframe *tf)
{
	int i;

	for (i = 0; i < NCOMMANDS; i++)
		cprintf("%s - %s\n", commands[i].name, commands[i].desc);
	return 0;
}

int
mon_kerninfo(int argc, char **argv, struct Trapframe *tf)
{
	extern char _start[], etext[], edata[], end[];

	cprintf("Special kernel symbols:\n");
	cprintf("  _start %08x (virt)  %08x (phys)\n", _start, _start - KERNBASE);
	cprintf("  etext  %08x (virt)  %08x (phys)\n", etext, etext - KERNBASE);
	cprintf("  edata  %08x (virt)  %08x (phys)\n", edata, edata - KERNBASE);
	cprintf("  end    %08x (virt)  %08x (phys)\n", end, end - KERNBASE);
	cprintf("Kernel executable memory footprint: %dKB\n",
		(end-_start+1023)/1024);
	return 0;
}

int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
	/*
	  Stack graph
	  (High addr)
	  arg4
	  arg3
	  arg2
	  arg1
	  arg0
	  old eip
	  old ebp <- ebp
	  (Low addr)
	*/

	uint32_t *last_ebp = (uint32_t *)read_ebp();
	unsigned last_eip = read_eip();
	cprintf("Stack backtrace:\n");

	while (last_ebp) {
		struct Eipdebuginfo dbg;
		int i;
		debuginfo_eip(last_eip, &dbg);
		// filename and line number
		cprintf("%s:%d: ", dbg.eip_file, dbg.eip_line);
		// function name
		for (i = 0; i < dbg.eip_fn_namelen; ++i)
			cputchar(*(dbg.eip_fn_name + i));
		// +offset and newline
		cprintf("+%x\n", last_eip - dbg.eip_fn_addr);

		cprintf("  ebp %08x  eip %08x  args %08x %08x %08x %08x %08x\n",
			(uint32_t)last_ebp, /* current ebp */
			*(last_ebp + 1), /* return eip */
			*(last_ebp + 2), /* arg 0 */
			*(last_ebp + 3), /* arg 1 */
			*(last_ebp + 4), /* arg 2 */
			*(last_ebp + 5), /* arg 3 */
			*(last_ebp + 6)  /* arg 4 */
			);
		last_eip = *(last_ebp + 1);
		last_ebp = (uint32_t *)(*last_ebp);
	}
	return 0;
}

int
mon_color(int argc, char **argv, struct Trapframe *tf)
{
	int i, j, k = 0;
	for (i = 0; i < argc; ++i) {
		const char *message = argv[i];
		j = 0;

		while (message[j]) {
			cprintf("\33[1;3%d;4%dm%c", k % 8, k % 8, message[j]);
			++j;
			++k;
		}

		cprintf("\33[0m");
		cputchar('\n');
	}
	return 0;
}

static uintptr_t
hex_to_la(char *str)
{
	uintptr_t res = 0, num = 0;
	if (str[0] && str[1] && str[0] == '0' && str[1] == 'x')
		str += 2;
	while (str
	       && ((*str >= '0' && *str <= '9')
		   || (*str >= 'a' && *str <= 'f')
		   || (*str >= 'A' && *str <= 'F'))) {
		res *= 16;
		switch (*str) {
		case '0' ... '9':
			num = *str - '0';
			break;
		case 'a' ... 'f':
			num = *str - 'a' + 10;
			break;
		case 'A' ... 'F':
			num = *str - 'A' + 10;
			break;
		}
		res += num;
		++str;
	}
	return res;
}
int
mon_showmappings(int argc, char **argv, struct Trapframe *tf)
{
	uintptr_t la_start = 0, la_end = (~PGSIZE) + 1, step = PGSIZE;
	if (argc == 1) {
		cprintf("Usage:\nshowmappings [start=0x%08x [end=0x%08x [granularity=1]]]\n- start: starting linear address\n- end: ending linear address(inclusive)\n- granularity: stepping unit, in 0x%x bytes\n", la_start, la_end, PGSIZE);
		return 0;
	}
	if (argc > 1)
		la_start = hex_to_la(argv[1]);
	if (argc > 2)
		la_end = hex_to_la(argv[2]);
	if (argc > 3)
		step = hex_to_la(argv[3]) * PGSIZE;
	cprintf("Mappings between 0x%08x and 0x%08x:\n",
		la_start, la_end);
	cprintf("VADDR      PADDR      U/S R/W\n");
	while (la_start <= la_end) {
		pte_t *res = NULL;
		physaddr_t paddr;
		if (boot_pgdir[PDX(la_start)] & PTE_P) {
			if (boot_pgdir[PDX(la_start)] & PTE_PS) {
				// 4MB page
				res = (pte_t *)(boot_pgdir + PDX(la_start));
				paddr = PTE_ADDR(*res) + PGOFF(la_start)
					+ (PTX(la_start) << PTXSHIFT);
			} else {
				// 4KB page
				res = (pte_t *)KADDR(PTE_ADDR(boot_pgdir[PDX(la_start)]));
				if (res[PTX(la_start)] & PTE_P) {
					res = res + PTX(la_start);
					paddr = PTE_ADDR(*res) + PGOFF(la_start);
				}
				else
					res = NULL;
			}
			if (res)
				cprintf("0x%08x 0x%08x  %c   %c\n",
					la_start, paddr,
					*res & PTE_U ? 'U' : 'S',
					*res & PTE_W ? 'W' : 'R'
					);
		}
		if (la_start <= la_end - step)
			la_start += step;
		else
			break;
	}
	return 0;
}

int
mon_setsu(int argc, char **argv, struct Trapframe *tf)
{
	uintptr_t la;
	pte_t *res;
	if (argc < 2) {
		cprintf("Usage:\nsetsu la\n- la: the linear address\n");
		return 0;
	}
	la = hex_to_la(argv[1]);
	res = (pte_t *)(boot_pgdir + PDX(la));
	if (!(*res & PTE_P)) {
		cprintf("0x%08x is not mapped yet. Nothing changed.\n", la);
		return 0;
	}
	if (*res & PTE_PS) {
		*res |= PTE_U;
	} else {
		res = (pte_t *)KADDR(PTE_ADDR(*res));
		if (!(*res & PTE_P))
			cprintf("0x%08x is not mapped yet. Nothing changed.\n", la);
		else
			*res |= PTE_U;
	}
	return 0;
}

int
mon_clrsu(int argc, char **argv, struct Trapframe *tf)
{
	uintptr_t la;
	pte_t *res;
	if (argc < 2) {
		cprintf("Usage:\nclrsu la\n- la: the linear address\n");
		return 0;
	}
	la = hex_to_la(argv[1]);
	res = (pte_t *)(boot_pgdir + PDX(la));
	if (!(*res & PTE_P)) {
		cprintf("0x%08x is not mapped yet. Nothing changed.\n", la);
		return 0;
	}
	if (*res & PTE_PS) {
		*res &= ~PTE_U;
	} else {
		res = (pte_t *)KADDR(PTE_ADDR(*res));
		if (!(*res & PTE_P))
			cprintf("0x%08x is not mapped yet. Nothing changed.\n", la);
		else
			*res &= ~PTE_U;
	}
	return 0;
}

int
mon_setrw(int argc, char **argv, struct Trapframe *tf)
{
	uintptr_t la;
	pte_t *res;
	if (argc < 2) {
		cprintf("Usage:\nsetrw la\n- la: the linear address\n");
		return 0;
	}
	la = hex_to_la(argv[1]);
	res = (pte_t *)(boot_pgdir + PDX(la));
	if (!(*res & PTE_P)) {
		cprintf("0x%08x is not mapped yet. Nothing changed.\n", la);
		return 0;
	}
	if (*res & PTE_PS) {
		*res |= PTE_W;
	} else {
		res = (pte_t *)KADDR(PTE_ADDR(*res));
		if (!(*res & PTE_P))
			cprintf("0x%08x is not mapped yet. Nothing changed.\n", la);
		else
			*res |= PTE_W;
	}
	return 0;
}

int
mon_clrrw(int argc, char **argv, struct Trapframe *tf)
{
	uintptr_t la;
	pte_t *res;
	if (argc < 2) {
		cprintf("Usage:\nclrrw la\n- la: the linear address\n");
		return 0;
	}
	la = hex_to_la(argv[1]);
	res = (pte_t *)(boot_pgdir + PDX(la));
	if (!(*res & PTE_P)) {
		cprintf("0x%08x is not mapped yet. Nothing changed.\n", la);
		return 0;
	}
	if (*res & PTE_PS) {
		*res &= ~PTE_W;
	} else {
		res = (pte_t *)KADDR(PTE_ADDR(*res));
		if (!(*res & PTE_P))
			cprintf("0x%08x is not mapped yet. Nothing changed.\n", la);
		else
			*res &= ~PTE_W;
	}
	return 0;
}

static int
is_mapped(pde_t *pgdir, uintptr_t la)
{
	pte_t *res = (pte_t *)(pgdir + PDX(la));
	if (!(*res & PTE_P))
		return 0;
	if (*res & PTE_PS)
		return 1;
	res = (pte_t *)KADDR(PTE_ADDR(*res));
	return *res & PTE_P;
}

int
mon_dumpla(int argc, char **argv, struct Trapframe *tf)
{
	uintptr_t la_start = 0, la_end = (~PGSIZE) + 1;
	uint8_t *curptr, *endptr;
	int count = 0;
	if (argc < 3) {
		cprintf("Usage:\ndumpla start end\n- start: starting linear address\n- end: ending linear address(inclusive)\n");
		return 0;
	}
	la_start = hex_to_la(argv[1]);
	la_end = hex_to_la(argv[2]);
	cprintf("Memory contents between 0x%08x and 0x%08x in linear address:\n",
		la_start, la_end);
	curptr = (uint8_t *)la_start;
	endptr = (uint8_t *)la_end;
	while (curptr <= endptr) {
		if (count == 0)
			cprintf("0x%08x ", (uintptr_t)curptr);
		if (is_mapped(boot_pgdir, (uintptr_t)curptr))
			cprintf("%02x", *curptr);
		else
			cprintf("XX");
		++count;
		if (count % 16 == 0)
		{
			count = 0;
			cputchar('\n');
		}
		else
			cputchar(' ');
		++curptr;
	}
	if (count % 16 != 0)
		cputchar('\n');
	return 0;
}

int
mon_dumppa(int argc, char **argv, struct Trapframe *tf)
{
	physaddr_t pa_start, pa_end;
	uint8_t *curptr, *endptr;
	int count = 0;
	if (argc < 3) {
		cprintf("Usage:\ndumppa start end\n- start: starting physical address\n- end: ending physical address(inclusive)\n");
		return 0;
	}
	pa_start = hex_to_la(argv[1]);
	pa_end = hex_to_la(argv[2]);
	cprintf("Memory contents between 0x%08x and 0x%08x in physical address:\n",
		pa_start, pa_end);
	curptr = (uint8_t *)(KADDR(pa_start));
	endptr = (uint8_t *)(KADDR(pa_end));
	while (curptr <= endptr) {
		if (count == 0)
			cprintf("0x%08x ", PADDR((uintptr_t)curptr));
		if (is_mapped(boot_pgdir, (uintptr_t)curptr))
			cprintf("%02x", *curptr);
		else
			cprintf("XX");
		++count;
		if (count % 16 == 0)
		{
			count = 0;
			cputchar('\n');
		}
		else
			cputchar(' ');
		++curptr;
	}
	if (count % 16 != 0)
		cputchar('\n');
	return 0;
}

int
mon_pgstat(int argc, char **argv, struct Trapframe *tf)
{
	uint32_t pg_start, pg_end;
	if (argc < 3) {
		cprintf("Usage:\npgstat start end\n- start: starting physical address\n- end: ending physical address(inclusive)\n");
		return 0;
	}
	pg_start = hex_to_la(argv[1]) / PGSIZE;
	pg_end = hex_to_la(argv[2]);
	pg_end = MIN(pg_end % PGSIZE ? pg_end / PGSIZE + 1 : pg_end / PGSIZE, npage - 1);
	cprintf("0x%05x physical pages in total. Showing from 0x%05x to 0x%05x\n", npage, pg_start, pg_end);
	cprintf(" Page# | Allocation | Ref Count\n");
	while (pg_start <= pg_end) {
		struct Page *pp = pages + pg_start;
		cprintf("0x%05x     %s     %10d\n", pg_start, in_page_free_list(pp) ? "FREE" : "USED", pp->pp_ref);
		++pg_start;
	}
	return 0;
}

int
mon_alloc_page(int argc, char **argv, struct Trapframe *tf)
{
	struct Page *pp;
	if (page_alloc(&pp) == 0)
		cprintf("\t0x%05x\n", pp - pages);
	else
		cprintf("\tOut of memory\n");
	return 0;
}

int
mon_free_page(int argc, char **argv, struct Trapframe *tf)
{
	uint32_t pg_idx;
	struct Page *pp;
	if (argc < 2) {
		cprintf("Usage:\nfree_page idx\n- idx: the page index, ranging from 0 to %x\n", npage - 1);
		return 0;
	}
	pg_idx = hex_to_la(argv[1]);
	if (pg_idx >= npage) {
		cprintf("\tInvalid page index: %x\n", pg_idx);
		return 0;
	}
	pp = pages + pg_idx;
	page_free(pp);
	return 0;
}

int
mon_page_status(int argc, char **argv, struct Trapframe *tf)
{
	uint32_t pg_idx;
	struct Page *pp;
	if (argc < 2) {
		cprintf("Usage:\npage_status idx\n- idx: the page index, ranging from 0 to %x\n", npage - 1);
		return 0;
	}
	pg_idx = hex_to_la(argv[1]);
	if (pg_idx >= npage) {
		cprintf("\tInvalid page index: %x\n", pg_idx);
		return 0;
	}
	pp = pages + pg_idx;
	cprintf("\t%s\n", in_page_free_list(pp) ? "free" : "allocated");
	return 0;
}

/* int */
/* mon_step(int argc, char **argv, struct Trapframe *tf) */
/* { */
/* } */

/* int */
/* mon_continue(int argc, char **argv, struct Trapframe *tf) */
/* { */
/* } */

/* int */
/* mon_disasm(int argc, char **argv, struct Trapframe *tf) */
/* { */
/* } */

/***** Kernel monitor command interpreter *****/

#define WHITESPACE "\t\r\n "
#define MAXARGS 16

static int
runcmd(char *buf, struct Trapframe *tf)
{
	int argc;
	char *argv[MAXARGS];
	int i;

	// Parse the command buffer into whitespace-separated arguments
	argc = 0;
	argv[argc] = 0;
	while (1) {
		// gobble whitespace
		while (*buf && strchr(WHITESPACE, *buf))
			*buf++ = 0;
		if (*buf == 0)
			break;

		// save and scan past next arg
		if (argc == MAXARGS-1) {
			cprintf("Too many arguments (max %d)\n", MAXARGS);
			return 0;
		}
		argv[argc++] = buf;
		while (*buf && !strchr(WHITESPACE, *buf))
			buf++;
	}
	argv[argc] = 0;

	// Lookup and invoke the command
	if (argc == 0)
		return 0;
	for (i = 0; i < NCOMMANDS; i++) {
		if (strcmp(argv[0], commands[i].name) == 0)
			return commands[i].func(argc, argv, tf);
	}
	cprintf("Unknown command '%s'\n", argv[0]);
	return 0;
}

void
monitor(struct Trapframe *tf)
{
	char *buf;

	cprintf("Welcome to the JOS kernel monitor!\n");
	cprintf("Type 'help' for a list of commands.\n");

	if (tf != NULL)
		print_trapframe(tf);

	while (1) {
		buf = readline("K> ");
		if (buf != NULL)
			if (runcmd(buf, tf) < 0)
				break;
	}
}

// return EIP of caller.
// does not work if inlined.
// putting at the end of the file seems to prevent inlining.
unsigned
read_eip()
{
	uint32_t callerpc;
	__asm __volatile("movl 4(%%ebp), %0" : "=r" (callerpc));
	return callerpc;
}
